// Copyright  2004-2008 Backplane Ltd.
//
// This file is part of Ubiquity formsPlayer
//
// Ubiquity formsPlayer is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
//
// Ubiquity formsPlayer is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License
// along with Ubiquity formsPlayer If not, see <http://www.gnu.org/licenses/>.
//	
//
//   Purpose: Implementation of CImplActionInsert.
//   $Id$

#include "stdafx.h"
#include "actioninsert.h"
#include "XFormsElementinsert.h"
#include "XFormsXFormsElement.h"
#include "model.h"
#include "ImplBind.h"
#include "XPathResultNodesetWrapper.h"
#include "XMLDomNodeNodesetWrapper.h"
CImplActionInsert::CImplActionInsert(CXFormsElementInsert *pXFEl)
{
	m_pXFormsElement = pXFEl;
	pXFEl->AddRef();
}

CImplActionInsert::CImplActionInsert()
{

}

HRESULT CImplActionInsert::PerformAction()
{	
	HRESULT hr = S_FALSE;
		
	ResolveEvaluationContext();

	if(m_pXForms->canContinue())
	{
		if(!m_pModel)
			ResolveEvaluationContext();

        m_pXForms->IncreaseDeferredUpdateCnt();
		m_pXForms->putXPathIdentityResolver(GetIdResolver());

		CComPtr<IXMLDOMNode> pInScopeEvaluationContext;
		GetXPathContext(&pInScopeEvaluationContext);

		CComPtr<IXMLDOMNode> pInsertContext;
		hr = DetermineInsertContext(pInScopeEvaluationContext,&pInsertContext);
		if(S_OK == hr)
		{
			CPointerContainer<INodeset> pNodesetBindingNodeset;
			
			hr = DetermineNodeSetBindingNodeset(pInsertContext,pNodesetBindingNodeset);
			if(SUCCEEDED(hr))
			{
				CPointerContainer<INodeset> pOriginNodeset;
				hr = DetermineOriginNodeset(pInsertContext,pNodesetBindingNodeset,pOriginNodeset);
				if(S_OK == hr)
				{
					CComPtr<IXMLDOMNode> pInsertLocationNode;
					hr = DetermineInsertLocationNode(pInsertContext,pNodesetBindingNodeset,&pInsertLocationNode);
					if(S_OK == hr)
					{
						hr = CopyOriginToTarget(pOriginNodeset,pNodesetBindingNodeset,pInsertLocationNode);
						if(SUCCEEDED(hr))
						{
							hr = NotifyCompletion();
						}
					}
				}
			}
		}

		m_pXForms->putXPathIdentityResolver(0);
		m_pXForms->DecreaseDeferredUpdateCnt();
	}
	return hr;
}

HRESULT CImplActionInsert::NotifyCompletion()
{
	m_pModel->FireEvent(EVENT_INSERT);
	//it must be true, as before this call, the updatecount is incremented
	HRESULT hr = m_pModel->rebuild(true);
	if (S_OK == hr)
		hr =  m_pModel->recalculate(true);
	if (S_OK == hr)
		hr =  m_pModel->revalidate(true);
	if (S_OK == hr)
		hr =  m_pModel->refresh(true);
	return hr;
}

/*
if the bind attribute is present or if the context attribute is not given, 
	the insert context is the in-scope evaluation context.
Otherwise, the XPath expression provided by the context attribute is evaluated using the in-scope evaluation context,
	and the first node rule is applied to obtain the insert context. 
The insert action is terminated with no effect if the insert context is the empty node-set,
	or if the insert context is not an element.
*/
HRESULT  CImplActionInsert::DetermineInsertContext(IXMLDOMNode * pEvaluationContext, IXMLDOMNode ** ppInsertContext)
{
	HRESULT hr = S_FALSE;
	// the context attribute has already been evaluated in returning the inscope context
	//	All this function is really doing is working out whether it is a suitable context.
	*ppInsertContext = pEvaluationContext;
	if(pEvaluationContext)
	{
		pEvaluationContext->AddRef();
		hr = S_OK;
	}

	if(!*ppInsertContext)
	{
		hr = S_FALSE;
	}
	else
	{
		DOMNodeType t;
		hr = (*ppInsertContext)->get_nodeType(&t);

		if(NODE_ELEMENT == t)
		{
			hr = S_OK;	
		}
		else
		{
			hr = S_FALSE;
		}
	}

	return hr;
}

/*

The Node Set Binding node-set is determined.
If a bind attribute is present, it directly determines the Node Set Binding node-set.
If a nodeset attribute is present, it is evaluated within the insert context to determine the Node Set Binding node-set. 
If the Node Set Binding attributes are not present, 
	then the Node Set Binding node-set is the empty node-set. 
The insert action is terminated with no effect if any of the following conditions is true:
   1.
      The context attribute is not given and the Node Set Binding node-set is the empty node-set.
   2.
      The context attribute is given, the insert context does not evaluate to an element node and the Node Set Binding node-set is the empty node-set. 
*/


HRESULT  CImplActionInsert::DetermineNodeSetBindingNodeset(IXMLDOMNode * pInsertContext,CPointerContainer<INodeset>& pNodesetBindingNodeset)
{
	HRESULT hr = S_FALSE;
	if(m_sBind)
	{
		CImplBind * pBind;
		hr = m_pXForms->getBind(m_sBind,pBind);
		if(pBind)
		{
			pNodesetBindingNodeset.m_bDeleteOnDestroy = false;
			pNodesetBindingNodeset.m_p = pBind;
		}
	}
	else if(m_sNodeset)
	{
		CComPtr<IXPathResult> pResult;
		hr = m_pModel->getInstanceNodeList(m_sNodeset,pInsertContext,false,&pResult);
		if(SUCCEEDED(hr))
		{
			pNodesetBindingNodeset.m_bDeleteOnDestroy = true;
			pNodesetBindingNodeset.m_p = new CXPathResultNodesetWrapper(pResult);
		}
	}

	return hr;
}

//The origin node-set is determined. 
//	If the origin attribute is not given and the Node Set Binding node-set is empty, 
//		then the origin node-set is the empty node-set. 
//	Otherwise, if the origin attribute is not given, 
//		then the origin node-set  consists of the last node of the Node Set Binding node-set. 
//	If the origin attribute is given, 
//		the origin node-set is the result of the evaluation of the origin  attribute in the insert context.
//	The insert action is terminated with no effect if the origin node-set is the empty node-set.

HRESULT  CImplActionInsert::DetermineOriginNodeset(IXMLDOMNode * pInsertContext,INodeset * pNodesetBindingNodeset,CPointerContainer<INodeset>& pOriginNodeset)
{
	HRESULT hr = S_FALSE;
	
	if(m_sOrigin)
	{
		CComPtr<IXPathResult> pResult;
		hr = m_pModel->getInstanceNodeList(m_sOrigin,pInsertContext,false,&pResult);
		if(SUCCEEDED(hr))
		{
			pOriginNodeset.m_bDeleteOnDestroy = true;
			pOriginNodeset.m_p = new CXPathResultNodesetWrapper(pResult);
		}
	}
	else if(pNodesetBindingNodeset)
	{
		long l = pNodesetBindingNodeset->GetNodesetLength();
		if(l > 0)
		{
			CComPtr<IXMLDOMNode> pNode;
			pNodesetBindingNodeset->GetNodeAt(l-1,&pNode);
			
			pOriginNodeset.m_bDeleteOnDestroy = true;
			pOriginNodeset.m_p = new CXMLDomNodeNodesetWrapper(pNode);
		}
	}

	if(pOriginNodeset.m_p && pOriginNodeset.m_p->GetNodesetLength() > 0)
	{
		hr = S_OK;
	}
	else
	{
		hr = S_FALSE;
	}
	return hr;
}
/*
The insert location node is determined. 

If the Node Set Binding node-set is not specified or empty, the insert location node is the insert context node. 

Otherwise, if the at attribute is not given, 
	then the insert location node is the last node of the Node Set Binding node-set. 

Otherwise, an insert location node is determined from the at attribute as follows:

   1.
      The evaluation context node is the first node in document order from the Node Set Binding node-set,
		the context size is the size of the Node Set Binding node-set, and the context position is 1.
   2.

      The return value is processed according to the rules of the XPath function round(). 
		For example, the literal 1.5 becomes 2, and the literal 'string' becomes NaN.
   3.

    If the result is in the range 1 to the Node Set Binding node-set size, 
		then the insert location is equal to the result. 
	If the result is non-positive, then the insert location is 1. 
		Otherwise, the result is NaN or exceeds the Node Set Binding node-set size, 
			so the insert location is the Node Set Binding node-set size.
   4.
      The insert location node is the node in the Node Set Binding node-set at the position given by the insert location. 
*/
HRESULT  CImplActionInsert::DetermineInsertLocationNode(IXMLDOMNode * pInsertContext,INodeset * pNodesetBindingNodeset,IXMLDOMNode ** ppInsertLocationNode)
{
	HRESULT hr = S_FALSE;
	if(!pNodesetBindingNodeset || pNodesetBindingNodeset->GetNodesetLength() == 0)
	{
		*ppInsertLocationNode = pInsertContext;
		pInsertContext->AddRef();
		hr = S_OK;
	}
	else if(m_sAt)
	{
		unsigned long ulAt;
		CComPtr<IXMLDOMNode> pContextNode;
		pNodesetBindingNodeset->GetNodeAt(0,&pContextNode);
		long lMax = pNodesetBindingNodeset->GetNodesetLength() - 1;
		hr = m_pModel->getNodesetActionLocation(pContextNode,m_sAt,lMax,&ulAt);
		hr = pNodesetBindingNodeset->GetNodeAt(ulAt,ppInsertLocationNode);
	}
	else
	{
		long l = pNodesetBindingNodeset->GetNodesetLength();
		if(l > 0)
		{
			hr = pNodesetBindingNodeset->GetNodeAt(l-1,ppInsertLocationNode);
		}	
	}
	return hr;
}
/*
The target location of each cloned node or nodes is determined as follows:

   1.

    If the Node Set Binding node-set is not specified or empty,
		the target location depends on the node type of the cloned node.
	If the cloned node is an attribute,
		then the target location is before the first attribute of the insert location node.
	If the cloned node is not an attribute,
		then the target location is before the first child of the insert location node.

   2.

    Otherwise, 
		if the Node Set Binding node-set is specified and not empty 
			and the type of the cloned node is different from the type of the insert location node, 
					the target location depends on the node type of the cloned node.
		If the cloned node is an attribute, 
			then the target location is before the first attribute of the insert location node node.
		If the cloned node is not an attribute, 
			then the target location is before the first child of the insert location node.
   3.
	Otherwise,
		if insert location node is the root element of an instance, 
			then that instance root element location is the target location. 
		If there is more than one cloned node to insert, 
			only the first node that does not cause a conflict is considered.
   4.
      Otherwise, 
		the target location is immediately before or after the insert location node, 
			based on the position attribute setting or its default. 
*/
HRESULT  CImplActionInsert::CopyOriginToTarget(INodeset * pOriginNodeset, INodeset * pNodesetBindingNodeset, IXMLDOMNode * pInsertLocationNode)
{
	HRESULT hr = E_FAIL;
	
	long lOriginLength = pOriginNodeset->GetNodesetLength();
	DOMNodeType locationType;
	pInsertLocationNode->get_nodeType(&locationType);

	bool bNoNodesetBindingNodesetExists = false;

	if(!pNodesetBindingNodeset || pNodesetBindingNodeset->GetNodesetLength() == 0)
	{
		bNoNodesetBindingNodesetExists = true;
	}
	
	for(long l = 0;l < lOriginLength ;++l)
	{
		CComPtr<IXMLDOMNode> pOriginNode;
		pOriginNodeset->GetNodeAt(l,&pOriginNode);
		if(pOriginNode)
		{

			DOMNodeType originType;
			pOriginNode->get_nodeType(&originType);
			CComPtr<IXMLDOMNode> pOriginClone;
			pOriginNode->cloneNode(VARIANT_TRUE,&pOriginClone);

			if(bNoNodesetBindingNodesetExists || originType != locationType)
			{
				//insert into pInsertLocationNode
				if(originType == NODE_ATTRIBUTE)
				{
					CComQIPtr<IXMLDOMElement> pEl = pInsertLocationNode;
					if(pEl)
					{
						CComQIPtr<IXMLDOMAttribute> pOriginCloneAttribute = pOriginClone;
						hr = pEl->setAttributeNode(pOriginCloneAttribute,0);
					}
					else
					{
						hr = S_FALSE;
					}
				}
				else
				{

					CComPtr<IXMLDOMNode> pLocationFirstChild;
					pInsertLocationNode->get_firstChild(&pLocationFirstChild);
					CComVariant vtLocationFirstChild(pLocationFirstChild);
					CComPtr<IXMLDOMNode> pOut;
					hr = pInsertLocationNode->insertBefore(pOriginClone,vtLocationFirstChild,&pOut);
				}
			}
			else
			{
				if(locationType == NODE_ATTRIBUTE)
				{
					CComPtr<IXMLDOMNode> pInsertionParent;
					pInsertLocationNode->selectSingleNode(L"..",&pInsertionParent);
//Will not work - MSXML has no sensible way of inserting an attribute into an element in a particular position.
//					hr = pInsertionParent->insertBefore(pOriginClone,CComVariant(pInsertLocationNode),0);
					CComQIPtr<IXMLDOMElement> pEl = pInsertionParent;
					if(pEl)
					{
						CComQIPtr<IXMLDOMAttribute> pOriginCloneAttribute = pOriginClone;
						hr = pEl->setAttributeNode(pOriginCloneAttribute,0);
					}
					else
					{
						hr = S_FALSE;
					}
				}
				else
				{
					CComPtr<IXMLDOMNode> pInsertionParent;
					pInsertLocationNode->get_parentNode(&pInsertionParent);
					if(pInsertionParent)
					{
						DOMNodeType parentType;
						pInsertionParent->get_nodeType(&parentType);
						if(parentType == NODE_DOCUMENT)
						{
							hr = pInsertionParent->replaceChild(pOriginClone,pInsertLocationNode,0);
							if(SUCCEEDED(hr))
							{
								break;
							}
						}
						else
						{
							//insert as sibling to pInsertLocationNode
							if(m_sPosition == L"before")
							{
								CComVariant vtLocationNode(pInsertLocationNode);
								CComPtr<IXMLDOMNode> pOut;
								hr = pInsertionParent->insertBefore(pOriginClone,vtLocationNode,&pOut);
							}
							else
							{
								//after is default
								CComPtr<IXMLDOMNode> pInsertLocationFollowingSibling;
								pInsertLocationNode->get_nextSibling(&pInsertLocationFollowingSibling);
								//The DOM Handles NULL as at end.
								CComVariant vtLocationFollowingSibling(pInsertLocationFollowingSibling);
								CComPtr<IXMLDOMNode> pOut;
								hr = pInsertionParent->insertBefore(pOriginClone,vtLocationFollowingSibling,&pOut);
							}
						}
					}
				}


			}

		}
	}

	return hr;
}


/*
HRESULT CImplActionInsert::PerformAction()
{	
	HRESULT hr = S_FALSE;
		
	ResolveEvaluationContext();

	if(m_pXForms->canContinue())
	{
		if(!m_pModel)
			ResolveEvaluationContext();

        m_pXForms->IncreaseDeferredUpdateCnt();
		m_pXForms->putXPathIdentityResolver(GetIdResolver());
		CComPtr<IXMLDOMNode> pContext;
		GetXPathContext(&pContext);
		if(pContext)
		{
			//Get the target nodeset
			CImplBind * pBind = 0;
			CComPtr<IXPathResult> pResult;
			if(m_sBind)
			{
				m_pXForms->getBind(m_sBind,pBind);
			}
			else if(m_sNodeset)
			{
				m_pModel->getInstanceNodeList(m_sNodeset,pContext,false,&pResult);
			}
			
			CComPtr<IXMLDOMNode> pNodeToInsert;
			hr = GetNodeToInsert(pContext,pBind,pResult,&pNodeToInsert);
			if(pNodeToInsert)
			{
				CComPtr<IXMLDOMNode> pRelatedNode;
				hr = GetRelatedNode(pBind,pResult,&pRelatedNode);
				if(SUCCEEDED(hr))
				{
					hr = InsertNode(pContext,pNodeToInsert,pRelatedNode);
					if(SUCCEEDED(hr))
					{
					//notify the host and run custom event handlers.
						m_pModel->FireEvent(EVENT_INSERT);

						
						// Allow the form to be refreshed
						

						hr = m_pModel->rebuild(m_pXForms->IsInDeferredUpdate());
						if (S_OK == hr)
							hr =  m_pModel->recalculate(m_pXForms->IsInDeferredUpdate());
						if (S_OK == hr)
							hr =  m_pModel->revalidate(m_pXForms->IsInDeferredUpdate());
						if (S_OK == hr)
							hr =  m_pModel->refresh(m_pXForms->IsInDeferredUpdate());	
					}
				}
			}

		}
		m_pXForms->putXPathIdentityResolver(0);
		m_pXForms->DecreaseDeferredUpdateCnt();
	}
	return hr; 
}
*/
HRESULT CImplActionInsert::InsertNode(IXMLDOMNode * pContext, IXMLDOMNode * pNodeToInsert,IXMLDOMNode *pRelatedNode)
{
	
	CComPtr<IXMLDOMNode> pInsertionParent;
	
	HRESULT hr = S_FALSE;
	
	if(pRelatedNode)
		hr = pRelatedNode->get_parentNode(&pInsertionParent);
	else
	{
		CComPtr<IXMLDOMNode> pNewChild;
		hr = pContext->get_firstChild(&pRelatedNode);
		CComQIPtr<IXMLDOMDocument> pDoc;
		pContext->get_ownerDocument(&pDoc);
		if(pDoc == pContext || (!pDoc && (pDoc = pContext)))
		{
			CComPtr<IXMLDOMElement> pEl;
			pDoc->get_documentElement(&pEl);
			CComQIPtr<IXMLDOMNode> pDocumentElementAsNode(pEl);
			if(pDocumentElementAsNode)
			pDoc->replaceChild(pNodeToInsert,pDocumentElementAsNode,&pNewChild);
		}
		else
		{
			pContext->insertBefore(pNodeToInsert,CComVariant(pRelatedNode),&pNewChild);
		}
	}
	if(pInsertionParent)
	{
		CComQIPtr<IXMLDOMDocument> pDoc;
		pInsertionParent->get_ownerDocument(&pDoc);
		CComPtr<IXMLDOMNode> pNewChild;
		if(pDoc == pInsertionParent || (!pDoc && (pDoc = pInsertionParent)))
		{
			CComPtr<IXMLDOMElement> pEl;
			pDoc->get_documentElement(&pEl);
			CComQIPtr<IXMLDOMNode> pDocumentElementAsNode(pEl);
			if(pDocumentElementAsNode)
				pDoc->replaceChild(pNodeToInsert,pDocumentElementAsNode,&pNewChild);
		}
		else
		{
			DOMNodeType type;
			pNodeToInsert->get_nodeType(&type);
			if(type == NODE_ATTRIBUTE)
			{
				CComQIPtr<IXMLDOMElement> pEl = pInsertionParent;
				if(pEl)
				{
					CComBSTR s;
					CComVariant v;
					pNodeToInsert->get_nodeName(&s);
					pNodeToInsert->get_nodeValue(&v);
					hr = pEl->setAttribute(s,v);
				}
				else
				{
					hr = S_FALSE;
				}

			}
			else
			{
				CComPtr<IXMLDOMNode> pReferencePoint;
				if(m_sPosition==L"before")
				{
					pReferencePoint = pRelatedNode;
				}
				else if(!m_sPosition || m_sPosition == L"after")
				{
					hr = pRelatedNode->get_nextSibling(&pReferencePoint);
				}
				hr = pInsertionParent->insertBefore(pNodeToInsert,CComVariant(pReferencePoint),&pNewChild);
			}
		}
	}
	return hr;
}

HRESULT CImplActionInsert::GetRelatedNode(CImplBind * pBind,IXPathResult * pResult, IXMLDOMNode ** ppRelatedNode)
{
	HRESULT hr = S_FALSE;
	CComPtr<IXMLDOMNode> pContextNode;
	unsigned long max = 0;
	if(pBind)
	{
		max = pBind->GetNodesetLength() - 1;
		hr = pBind->GetNodeAt(0,&pContextNode);
	}
	else if(pResult)
	{
		hr = pResult->get_snapshotLength(&max);
		--max;
		if(SUCCEEDED(hr))
			hr = pResult->snapshotItem(0,&pContextNode);
	}

	unsigned long ulAt;
	if(m_sAt)
	{
		hr = m_pModel->getNodesetActionLocation(pContextNode,m_sAt,max,&ulAt);
	}
	else
	{
		ulAt = max;
	}
	
	if(pBind)
		hr = pBind->GetNodeAt(ulAt,ppRelatedNode);
	else if(pResult)
			hr = pResult->snapshotItem(ulAt,ppRelatedNode);
	
	return hr;
}

HRESULT CImplActionInsert::GetNodeToInsert(IXMLDOMNode * pContext,CImplBind * pBind,IXPathResult * pResult, IXMLDOMNode ** ppNodeToInsert)
{
	HRESULT hr;
	CComPtr<IXMLDOMNode> pNodeTemplateToInsert;
	if(m_sOrigin)
	{
		hr = m_pModel->getInstanceNode(m_sOrigin,pContext,false,&pNodeTemplateToInsert);
	}
	else if(pBind)
	{
		long max = pBind->GetNodesetLength() - 1;
		hr = pBind->GetNodeAt(max,&pNodeTemplateToInsert);
	}
	else
	{
		ULONG listLen;
		hr = pResult->get_snapshotLength(&listLen);
		if(listLen)
			hr = pResult->snapshotItem(listLen - 1,&pNodeTemplateToInsert);
	}

	if(pNodeTemplateToInsert)
	{
		hr = pNodeTemplateToInsert->cloneNode(VARIANT_TRUE,ppNodeToInsert);
	}

	return hr;
}



void CImplActionInsert::ResolveBindings()
{
}